package com.woncode.Util;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.util.*;

/**
 * Created by Wang Guiwen on 2017/9/15.
 */
public class InitMethodExecutor {

    /**
     * Scans all classes accessible from the context class loader which belong
     * to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private Iterable<Class> getAllClassByPackageName(String packageName) throws Exception {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            URI uri = new URI(resource.toString());
            dirs.add(new File(uri.getPath()));
        }
        List<Class> classes = new ArrayList<>();
        for (File directory : dirs) {
            classes.addAll(findAllClass(directory, packageName));
        }

        return classes;
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    private List<Class> findAllClass(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList<>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                classes.addAll(findAllClass(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
            }
        }
        return classes;
    }


    /**
     * 获取一个类中使用某个注释的所有方法
     *
     * @param type class
     * @param annotation
     * @return
     */
    private List<Method> getAllMethodWithAnnotatedByClass(Class<?> type, final Class<? extends Annotation> annotation) {
        final List<Method> methods = new ArrayList<Method>();
        while (type != Object.class) { // need to iterated thought hierarchy in order to retrieve methods from above the current instance
            // iterate though the list of methods declared in the class represented by class variable, and add those annotated with the specified annotation
            final List<Method> allMethods = new ArrayList<Method>(Arrays.asList(type.getDeclaredMethods()));
            for (final Method method : allMethods) {
                if (method.isAnnotationPresent(annotation)) {
                    methods.add(method);
                }
            }
            // move to the upper class in the hierarchy in search for more methods
            type = type.getSuperclass();
        }
        return methods;
    }


    //获取一个包中使用某个注释的所有方法
    public List<Method> getAllMethodWithAnnotatedByPackageName(String PackageName, Class<? extends Annotation> annotation) throws Exception {
        List<Method> methodList = new LinkedList<>();
        Iterator<Class> iterator = getAllClassByPackageName(PackageName).iterator();
        while (iterator.hasNext()){
            methodList.addAll(getAllMethodWithAnnotatedByClass(iterator.next(), annotation));
        }
        return methodList;
    }

    //执行一个包内使用InitMethodAnnotation注释的所有方法
    public void executeAllInitMethodByPackageName(String PackageName) throws Exception {
        List<Method> methodList = getAllMethodWithAnnotatedByPackageName(PackageName, InitMethodAnnotation.class);

        methodList.sort(new Comparator<Method>() {
            @Override
            public int compare(Method o1, Method o2) {
                InitMethodAnnotation o1Annotation = o1.getAnnotation(InitMethodAnnotation.class);
                InitMethodAnnotation o2Annotation = o2.getAnnotation(InitMethodAnnotation.class);
                if (o1Annotation.order() > o2Annotation.order()) {
                    return 1;
                } else if (o1Annotation.order() < o2Annotation.order()) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });

        for (Method method: methodList){
            method.invoke(method.getDeclaringClass().newInstance());
        }
    }
}
